
#include "../net/Broker4.h"
#include "../iface/APRInterface.h"
#include <apr-1/apr_strings.h>
#include "BaseConvert.h"
#include "StringUtils.h"
#include "StreamToDirectory.h"
#include "StreamFromDirectory.h"

#define min_v(a, b) (((a) < (b)) ? (a) : (b))

StreamFromDirectory::StreamFromDirectory(const char *directorypath, apr_pool_t *imp)
{
   apr_status_t status;
   status = apr_pool_create(&mp, imp);
   CheckAPRError(status);
   directory_name = (char *)strdup(directorypath);
   char *directory_name2 = (char *)strdup(directorypath);
   dirnames.push_back(directory_name2);
   currfinfopntr = NULL;
   currflag = 0;
   currbytesthrough = 0;
}

StreamFromDirectory::~StreamFromDirectory()
{
   if (directory_name) free(directory_name);
   directory_name = NULL;
   apr_pool_destroy(mp);
}

apr_off_t StreamFromDirectory::getNextBlock(char *blockbuffer, apr_off_t tsize)
{
   apr_status_t status;
   int count = 0;
   if (currdirpntrs.size() == 0)
   {
      apr_dir_t *dirpntr;
      apr_dir_open(&dirpntr, dirnames.back(), mp);
      currdirpntrs.push(dirpntr);
   }
   while ((count < tsize) && (currdirpntrs.size() > 0))
   {
      if (currfinfopntr == NULL)
      {
         currfinfopntr = (apr_finfo_t *)malloc(sizeof(apr_finfo_t));
         status = apr_dir_read(currfinfopntr,
            APR_FINFO_SIZE | APR_FINFO_TYPE | APR_FINFO_NAME,
            currdirpntrs.top());
         if (APR_STATUS_IS_ENOENT(status))
         {
            status = apr_dir_close(currdirpntrs.top());
            CheckAPRError(status);
            currdirpntrs.pop();
            free(dirnames.back());
            dirnames.pop_back();
            free(currfinfopntr);
            currfinfopntr = NULL;
            if (currdirpntrs.size() == 0) return count;
            continue;
         }
         CheckAPRError(status);
         currflag = WRITING_FILE_NAME_SIZE;
         currbytesthrough = 0;
      }
      switch (currfinfopntr->filetype)
      {
         case APR_REG:
         {
            apr_off_t tlen = tsize - count;
            int retval = writeDataOnFile(&(blockbuffer[count]), &tlen);
            if (retval == 0)
            {
               free(currfinfopntr);
               currfinfopntr = NULL;
            }
            if (retval >= 0) count += tlen;
            break;
         }
         case APR_DIR:
         {
            apr_dir_t *dirpntr;
            if ((strcmp(currfinfopntr->name, ".") == 0) ||
                (strcmp(currfinfopntr->name, "..") == 0))
            {
               free(currfinfopntr);
               currfinfopntr = NULL;
               break;
            }
            char *pathto = getPathToThisFile();
            status = apr_dir_open(&dirpntr, pathto, mp);
            free(pathto);
            CheckAPRError(status);
            if (dirpntr != NULL)
            {
               currdirpntrs.push(dirpntr);
               dirnames.push_back(strdup(currfinfopntr->name));
            } else
            {
               currdirpntrs.pop();
               free(dirnames.back());
               dirnames.pop_back();
            }
            free(currfinfopntr);
            currfinfopntr = NULL;
            break;
         }
         default:
            free(currfinfopntr);
            currfinfopntr = NULL;
            break;
      }
   }
   return count;
}

char *StreamFromDirectory::getLocalPathToThisFile()
{
   int totlen = 0;
   for (int i = 1; i < dirnames.size(); i++)
   {
      totlen += strlen(dirnames.at(i));
      totlen += 1;
   }
   char *path_to_here = (char *)malloc(sizeof(char)*(totlen+1));
   path_to_here[0] = '\0';
   for (int i = 1; i < dirnames.size(); i++)
   {
      strcat(path_to_here, dirnames.at(i));
      strcat(path_to_here, "/");
   }
   char *path_plus =
      StringUtils::varprintf("%s%s", (path_to_here) ? (path_to_here) : "", currfinfopntr->name);
   free(path_to_here);
   return path_plus;
}

char *StreamFromDirectory::getPathToThisFile()
{
   char *path_to_here = StringUtils::flattenStringList(&dirnames, "/");
   char *path_plus = StringUtils::varprintf("%s/%s", path_to_here, currfinfopntr->name);
   free(path_to_here);
   return path_plus;
}

void StreamFromDirectory::writeLogical64Bit(int64_t towrite, unsigned char *bytes)
{
   int64_t wr = towrite;
   for (int i = 0; i < 8; i++)
   {
      bytes[i] = wr % 256;
      wr /= 256;
   }
   if (towrite != StreamToDirectory::readLogical64Bit(bytes))
      errprint("writing 64 bits as %016qX but read in as %qd which is not %qd\n",
         *((int64_t *)bytes), StreamToDirectory::readLogical64Bit(bytes), towrite);
}

int StreamFromDirectory::writeDataOnFile(char *dst, apr_off_t *maxlen)
{
   apr_status_t status;
   apr_off_t remlen = *maxlen;
   apr_off_t pnt = 0;
   char *filename = getPathToThisFile();
   char *localfilename = getLocalPathToThisFile();
   unsigned char b[8];
   if (currflag == WRITING_FILE_NAME_SIZE)
   {
      writeLogical64Bit(strlen(localfilename), b);
      apr_off_t by = min_v(8-currbytesthrough, remlen);
      memcpy(&(dst[pnt]), &(b[currbytesthrough]), by);
      pnt += by;
      remlen -= by;
      if (!remlen)
      {
         currbytesthrough += by;
         *maxlen = pnt; free(filename); free(localfilename);
         return 1;
      } else
      {
         currbytesthrough = 0;
         currflag = WRITING_FILE_STREAM_SIZE;
      }
   }
   if (currflag == WRITING_FILE_STREAM_SIZE)
   {
      writeLogical64Bit(currfinfopntr->size, b);
      apr_off_t by = min_v(8-currbytesthrough, remlen);
      memcpy(&(dst[pnt]), &(b[currbytesthrough]), by);
      pnt += by;
      remlen -= by;
      if (!remlen)
      {
         currbytesthrough += by;
         *maxlen = pnt; free(filename); free(localfilename);
         return 1;
      } else
      {
         currbytesthrough = 0;
         currflag = WRITING_NAME;
      }
   }
   if (currflag == WRITING_NAME)
   {
      apr_off_t by = min_v(strlen(localfilename)-currbytesthrough, remlen);
      memcpy(&(dst[pnt]), &(localfilename[currbytesthrough]), by);
      pnt += by;
      remlen -= by;
      if (!remlen)
      {
         currbytesthrough += by;
         *maxlen = pnt; free(filename); free(localfilename);
         return 1;
      } else
      {
         currbytesthrough = 0;
         currflag = WRITING_STREAM;
      }
   }
   if (currflag == WRITING_STREAM)
   {
      apr_off_t by = min_v((currfinfopntr->size)-currbytesthrough, remlen);
      apr_file_t *newFP;
      apr_status_t status;
      status = apr_file_open(&newFP, filename, APR_FOPEN_BINARY|APR_FOPEN_READ|APR_FOPEN_BUFFERED, APR_FPROT_OS_DEFAULT, mp);
      CheckAPRError(status);
      apr_off_t cbt = currbytesthrough;
      status = apr_file_seek(newFP, APR_SET, &cbt);
      CheckAPRError(status);
      apr_size_t bytesread = 0;
      status = apr_file_read_full(newFP, &(dst[pnt]), by, &bytesread);
      CheckAPRError(status);
      status = apr_file_close(newFP);
      CheckAPRError(status);
      by = bytesread;
      pnt += by;
      remlen -= by;
      if (!remlen)
      {
         currbytesthrough += by;
         *maxlen = pnt; free(filename); free(localfilename);
         return 1;
      } else
      {
         currbytesthrough = 0;
         currflag = WRITING_FILE_NAME_SIZE;
         *maxlen = pnt; free(filename); free(localfilename);
         return 0;
      }
   }
   *maxlen = pnt; free(filename); free(localfilename);
   return -1;
}
